using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using OpenTabletDriver.Analyzers.Formatters;
using OpenTabletDriver.Tablet;

namespace OpenTabletDriver.Analyzers.Emitters
{
    public sealed partial class DeviceConfigurationProviderEmitter
    {
        public const string INTERFACE_NAME = "global::OpenTabletDriver.Components.IDeviceConfigurationProvider";

        private readonly ProviderTarget _target;
        private readonly ImmutableArray<TabletConfiguration> _tabletConfigurations;

        public DeviceConfigurationProviderEmitter(ProviderTarget target, ImmutableArray<TabletConfiguration> tabletConfigurations)
        {
            _target = target;
            _tabletConfigurations = tabletConfigurations;
        }

        public string Emit()
        {
            var builder = new StringBuilder();
            builder.AppendLine("// <auto-generated />");
            builder.AppendLine();

            var compilationUnit = SyntaxFactory.CompilationUnit()
                .WithMembers(
                    SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
                        SyntaxFactory.NamespaceDeclaration(
                            SyntaxFactory.IdentifierName(_target.NamespaceName))
                        .WithMembers(
                            SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
                                SyntaxFactory.ClassDeclaration(_target.ClassName)
                                .WithModifiers(
                                    SyntaxFactory.TokenList(new[]
                                    {
                                        SyntaxFactory.Token(SyntaxKind.PartialKeyword)
                                    }))
                                .WithBaseList(
                                    SyntaxFactory.BaseList(
                                        SyntaxFactory.SingletonSeparatedList<BaseTypeSyntax>(
                                            SyntaxFactory.SimpleBaseType(
                                                SyntaxFactory.ParseTypeName(INTERFACE_NAME)))))
                                .WithMembers(
                                    SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
                                        SyntaxFactory.PropertyDeclaration(
                                            SyntaxFactory.GenericName(
                                                SyntaxFactory.Identifier($"global::System.Collections.Immutable.ImmutableArray"))
                                            .WithTypeArgumentList(
                                                SyntaxFactory.TypeArgumentList(
                                                    SyntaxFactory.SingletonSeparatedList(
                                                        SyntaxFactory.ParseTypeName(TabletConfigurationEmitter.CLASS_NAME)))),
                                            SyntaxFactory.Identifier("TabletConfigurations"))
                                        .WithModifiers(
                                            SyntaxFactory.TokenList(
                                                SyntaxFactory.Token(SyntaxKind.PublicKeyword)))
                                        .WithAccessorList(
                                            SyntaxFactory.AccessorList(
                                                SyntaxFactory.SingletonList(
                                                    SyntaxFactory.AccessorDeclaration(
                                                        SyntaxKind.GetAccessorDeclaration)
                                                    .WithSemicolonToken(
                                                        SyntaxFactory.Token(SyntaxKind.SemicolonToken)))))
                                        .WithInitializer(
                                            SyntaxFactory.EqualsValueClause(
                                                SyntaxFactory.InvocationExpression(
                                                    SyntaxFactory.MemberAccessExpression(
                                                        SyntaxKind.SimpleMemberAccessExpression,
                                                        SyntaxFactory.IdentifierName($"global::System.Collections.Immutable.ImmutableArray"),
                                                        SyntaxFactory.IdentifierName("Create")))
                                                .WithArgumentList(
                                                    SyntaxFactory.ArgumentList(
                                                        SyntaxFactory.SingletonSeparatedList(
                                                            SyntaxFactory.Argument(
                                                                SyntaxFactory.ImplicitArrayCreationExpression(
                                                                    SyntaxFactory.InitializerExpression(
                                                                        SyntaxKind.ArrayInitializerExpression,
                                                                        SyntaxFactory.SeparatedList<ExpressionSyntax>(
                                                                            _tabletConfigurations.Select(t => new TabletConfigurationEmitter(t).Emit()))))))))))
                                        .WithSemicolonToken(
                                            SyntaxFactory.Token(SyntaxKind.SemicolonToken))))))))
                .NormalizeWhitespace(eol: Environment.NewLine);

            builder.AppendLine(Format(compilationUnit).GetText().ToString());

            return builder.ToString();
        }

        private static SyntaxNode Format(CompilationUnitSyntax syntax)
        {
            // im not sure why this is necessary, but it is.
            // the formatter won't descend into the node's children otherwise
            syntax = (CompilationUnitSyntax)CSharpSyntaxTree.ParseText(syntax.ToString()).GetRoot();

            return new WhitespaceHumanizer().Visit(syntax);
        }
    }
}
